package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.discount.Discount;
import com.tianji.promotion.discount.DiscountStrategy;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.utils.*;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author author
 * @since 2025-02-15
 */
@Service
@RequiredArgsConstructor
public class UserCouponMqServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    private final CouponMapper couponMapper;
    private final IExchangeCodeService exchangeCodeService;
    private final RedissonClient redissonClient;
    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper mqHelper;
    private final ICouponScopeService couponScopeService;


    /**
     * 领取优惠券
     * @param id
     */
    @Override
    //@Transactional
    //分布式锁对优惠券加锁
    @MyLock(name = "lock:coupon:uid:#{id}")

    public void receiveCoupon(Long id) {
        //1.根据id查询优惠券信息 做相关校验
        if (id == null){
            throw new BadRequestException("非法参数");
        }
        //Coupon coupon = couponMapper.selectById(id);
        //从redis中获取优惠券信息
        Coupon coupon = queryCouponByCache(id);
        
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
//        if (coupon.getStatus() != CouponStatus.ISSUING){
//            throw new BadRequestException("优惠券不是正在发放");
//        }
        //校验发放时间
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
            throw new BadRequestException("优惠券发放已经结束或尚未开始");
        }
        //校验库存
        if (coupon.getIssueNum() <= 0) {
            throw new BadRequestException("优惠券库存不足");
        }
        //校验每人限领数量  统计当前用户对当前优惠券的已经领取的数量
        Long userId = UserContext.getUser();

        //通过redisson实现分布式锁
        /*String key = "lock:coupon:uid:"+userId;
        RLock lock = redissonClient.getLock(key);
        try {
            boolean isLock = lock.tryLock();
            if (!isLock){
                throw new BizIllegalException("操作频繁");
            }
            //从aop上下文中 获取当前类的代理对象
            IUserCouponService userCouponServiceProxy = (IUserCouponService)AopContext.currentProxy();
            //checkAndCreateUserCoupon(userId,coupon,null);//这种写法是调用原对象
            userCouponServiceProxy.checkAndCreateUserCoupon(userId,coupon,null);//调用代理对象的方法，方法是事务处理的
        }finally {
            lock.unlock();
        }*/

        //统计已领数量
        String key = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + id;
        //increment：领取后的 已领数量
        Long increment = redisTemplate.opsForHash().increment(key, userId.toString(), 1);

        //校验是否超过限领数量
        if (increment > coupon.getUserLimit()){ //由于increments +1 后的结果，所以不能等于
            throw new BizIllegalException("超出限领数量");
        }
        //修改优惠券库存 -1
        String couponKey = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;
        redisTemplate.opsForHash().increment(couponKey,"totalNum",-1);

        //发送消息到mq
        UserCouponDTO msg = new UserCouponDTO();
        msg.setUserId(userId);
        msg.setCouponId(id);
        mqHelper.send(
                MqConstants.Exchange.PROMOTION_EXCHANGE,
                MqConstants.Key.COUPON_RECEIVE,
                msg);

        /*//从aop上下文中 获取当前类的代理对象
        IUserCouponService userCouponServiceProxy = (IUserCouponService)AopContext.currentProxy();
        //checkAndCreateUserCoupon(userId,coupon,null);//这种写法是调用原对象
        userCouponServiceProxy.checkAndCreateUserCoupon(userId,coupon,null);//调用代理对象的方法，方法是事务处理的*/
    }

    /**
     * 从redis中获取优惠券信息(领取的开始和结束时间、发行数量、限领数量)
     * @param id
     * @return
     */
    private Coupon queryCouponByCache(Long id) {
        //1.拼接key
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;

        //2.从redis获取数据
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
        Coupon coupon = BeanUtils.mapToBean(entries, Coupon.class, false, CopyOptions.create());
        return coupon;
    }


    //保存用户券
    private void saveUserCoupon(Coupon coupon, Long userId) {
        // 1.基本信息
        UserCoupon uc = new UserCoupon();
        uc.setUserId(userId);
        uc.setCouponId(coupon.getId());
        // 2.有效期信息
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if (termBeginTime == null) {
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        uc.setTermBeginTime(termBeginTime);
        uc.setTermEndTime(termEndTime);
        // 3.保存
        save(uc);
    }


    /**
     * 兑换码兑换优惠券接口
     * @param code
     */
    @Override
    //@Transactional
    public void exchangeCoupon(String code) {
        //1.校验code是否为空
        if (StringUtils.isBlank(code)){
            throw new BadRequestException("非法参数");
        }

        //2.解析兑换码得到自增id
        long serialNum = CodeUtil.parseCode(code);

        //3.判断兑换码是否已兑换  采用redis的bitmap结构  setbit  key  offset  1  如果返回true：代表已兑换
        boolean result = exchangeCodeService.updateExchangeCodeMark(serialNum,true);
        if (result){
            //说明兑换码已经被兑换
            throw new BadRequestException("兑换码已被使用");
        }

        try{
            //4.判断兑换码是否存在  根据自增id查询 主键查询
            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
            if (exchangeCode==null){
                throw new BizIllegalException("兑换码不存在");
            }

            //5.判断是否过期
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime expiredTime = exchangeCode.getExpiredTime();
            if (now.isAfter(expiredTime)){
                throw new BizIllegalException("兑换码已经过期");
            }

            //校验生成用户券
            Long userId = UserContext.getUser();
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            if (coupon==null){
                throw new BizIllegalException("优惠券不存在");
            }
            checkAndCreateUserCoupon(userId, coupon,serialNum);
        }catch (Exception e){
            //将兑换码状态重置
            exchangeCodeService.updateExchangeCodeMark(serialNum,false);
            throw e;
        }
    }

    @Transactional
    @MyLock(name = "lock:coupon:uid:#{userId}",lockType = MyLockType.RE_ENTRANT_LOCK,lockStrategy = MyLockStrategy.FAIL_FAST)
    public void checkAndCreateUserCoupon(Long userId, Coupon coupon, Long serialNum) {
        //只要两个字符串一样，intern()返回的一定是同一个对象
        //synchronized (userId.toString().intern()){
            Integer count = this.lambdaQuery()
                    .eq(UserCoupon::getUserId, userId)
                    .eq(UserCoupon::getCouponId, coupon.getId())
                    .count();
            if(count != null && count >= coupon.getUserLimit()){
                throw new BadRequestException("超出领取数量");
            }

            //2.优惠券的已发数量+1
            couponMapper.incrIssueNum(coupon.getId());

            //3.生成用户券
            saveUserCoupon(coupon, userId);

            if (serialNum != null){
                //修改兑换码状态
                exchangeCodeService.lambdaUpdate()
                        .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                        .set(ExchangeCode::getUserId,userId)
                        .eq(ExchangeCode::getId,serialNum)
                        .update();
            }
        //}

    }

    @Transactional
    @Override
    public void checkAndCreateUserCouponNew(UserCouponDTO msg) {
//        Integer count = this.lambdaQuery()
//                .eq(UserCoupon::getUserId, userId)
//                .eq(UserCoupon::getCouponId, coupon.getId())
//                .count();
//        if(count != null && count >= coupon.getUserLimit()){
//            throw new BadRequestException("超出领取数量");
//        }

        msg.getCouponId();
        //1.从db中查询优惠券信息
        Coupon coupon = couponMapper.selectById(msg.getCouponId());
        if (coupon==null){
            return;
        }

        //2.优惠券的已发数量+1
        int num = couponMapper.incrIssueNum(coupon.getId());
        if (num==0){
            return;
        }
        //3.生成用户券
        saveUserCoupon(coupon, msg.getUserId());

//        if (serialNum != null){
//            //修改兑换码状态
//            exchangeCodeService.lambdaUpdate()
//                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
//                    .set(ExchangeCode::getUserId,userId)
//                    .eq(ExchangeCode::getId,serialNum)
//                    .update();
//        }
    }

    /**
     * 查询我的优惠券可用方案
     * @param orderCourses
     * @return
     */
    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> orderCourses) {
        //1.查询当前用户可用的优惠券 coupon和user_coupon表  条件：user_id,status=1  优惠券规则  优惠券id 用户券id
        List<Coupon> coupons = getBaseMapper().queryMyCoupns(UserContext.getUser());
        if (CollUtils.isEmpty(coupons)){
            return CollUtils.emptyList();
        }

        //2.初筛
        // 2.1.计算订单总价
        int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        // 2.2.筛选可用券
        List<Coupon> availableCoupons = coupons.stream()
                .filter(coupon -> DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalAmount, coupon))
                .collect(Collectors.toList());
        if (CollUtils.isEmpty(availableCoupons)) {
            return CollUtils.emptyList();
        }

        //3.细筛（考虑优惠券限定范围） 排列组合
        Map<Coupon,List<OrderCourseDTO>> avaMap = findAvailableCoupon(availableCoupons,orderCourses);
        if (avaMap.isEmpty()){
            return CollUtils.emptyList();
        }
        availableCoupons = new ArrayList<>(avaMap.keySet());//真正可用的优惠券集合

        //排列组合
        List<List<Coupon>> solutions = PermuteUtil.permute(availableCoupons);
        // 添加单券的方案
        for (Coupon c : availableCoupons) {
            solutions.add(List.of(c));
        }

        //4.计算每一种组合的优惠明细
        List<CouponDiscountDTO> list =
                Collections.synchronizedList(new ArrayList<>(solutions.size()));
        for (List<Coupon> solution : solutions) {
            list.add(calculateSolutionDiscount(avaMap, orderCourses, solution));
        }

        //5.使用多线程改造第四步 并行计算每种组合的优惠明细

        //6.筛选最优解
        return list;
    }

    /**
     * 计算每个方案的 优惠明细
     * @param avaMap 优惠券和可用课程的映射集合
     * @param orderCourses 订单中所有的课程
     * @param solution 方案
     * @return
     */
    private CouponDiscountDTO calculateSolutionDiscount(Map<Coupon, List<OrderCourseDTO>> avaMap,
                                                        List<OrderCourseDTO> orderCourses,
                                                        List<Coupon> solution) {
        //1.创建方案dto对象
        CouponDiscountDTO dto = new CouponDiscountDTO();
        //2.初始化商品id和商品折扣明细的映射，初始化折扣明细全设置为0
        Map<Long, Integer> detailMap = orderCourses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, orderCourseDTO -> 0));
        //3.计算该方案的折扣明细
        //3.1 循环方案中优惠券
        for (Coupon coupon : solution) {
            //3.2 取出该优惠券对应的可用课程
            List<OrderCourseDTO> availableCourses = avaMap.get(coupon);
            //3.3 计算可用课程的总金额（商品的价格 - 该商品的折扣明细）
            int totalAmount = availableCourses.stream()
                    .mapToInt(oc -> oc.getPrice() - detailMap.get(oc.getId())).sum();
            //3.4 判断优惠券是否可用
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (!discount.canUse(totalAmount, coupon)) {
                //券不可用，跳过
                continue;
            }
            //3.5 计算该优惠券使用后的折扣值
            int discountAmount = discount.calculateDiscount(totalAmount, coupon);
            //3.6 更新商品折扣明细（商品id的商品折扣明细）
            calculateDiscountDetails(detailMap, availableCourses, totalAmount, discountAmount);
            //3.7 累加每个优惠券的优惠金额 赋值给方案结果dto对象
            dto.getIds().add(coupon.getCreater());//只要执行当前这句话 就代表这个优惠券生效了
            dto.getRules().add(discount.getRule(coupon));
            dto.setDiscountAmount(discountAmount + dto.getDiscountAmount());
        }
        return dto;
    }

    /**
     * 计算商品 折扣明细
     * @param detailMap 商品id和商品的优惠明细 映射
     * @param availableCourses 当前优惠券可用的课程集合
     * @param totalAmount 可用课程的总金额
     * @param discountAmount 当前优惠券能优惠的金额
     */
    private void calculateDiscountDetails(Map<Long, Integer> detailMap,
                                          List<OrderCourseDTO> availableCourses,
                                          int totalAmount,
                                          int discountAmount) {
        //目的：本方法就是优惠券使用后 计算每个商品的折扣明细
        //规则：前面的商品按比例计算，最后一个商品折扣明细= 总的优惠金额 - 前面商品优惠的金额
        int times = 0; //已处理的商品个数
        int remainDiscount = discountAmount;//剩余的优惠金额
        for (OrderCourseDTO c : availableCourses) {
            times++;
            int discount = 0;
            if (times == availableCourses.size()) {
                //说明最后一个课程 总折扣金额 - 之前所有商品的折扣金额之和
                discount = remainDiscount;
            } else {
                //是前面的课程 按比例（课程价格在总价中占的比例，乘以总的折扣）
                discount = discountAmount * c.getPrice() / totalAmount;
                remainDiscount -= discount;
            }
            // 更新折扣明细
            detailMap.put(c.getId(), discount + detailMap.get(c.getId()));
        }
    }


    /**
     * 细筛 查询每一个优惠券 对应的可用课程
     * @param coupons 初筛后的优惠券集合
     * @param orderCourses 订单中的课程集合
     * @return
     */
    private Map<Coupon, List<OrderCourseDTO>> findAvailableCoupon(List<Coupon> coupons,
                                                                  List<OrderCourseDTO> orderCourses) {
        Map<Coupon, List<OrderCourseDTO>> map = new HashMap<>();
        //1.循环遍历初筛后的优惠券集合
        for (Coupon coupon : coupons) {
            List<OrderCourseDTO> availableCourse = orderCourses;
            //2.找出每个优惠券的可用课程
            //2.1判断优惠券是否限定了范围 coupon.specific=true
            if (coupon.getSpecific()){
                //2.2查询限定范围 查询coupon_scope表  条件：coupon_id
                List<CouponScope> scopeList = couponScopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();
                //2.3得到限定范围的id集合
                List<Long> scopeIds = scopeList.stream().map(CouponScope::getBizId).collect(Collectors.toList());
                //2.4从orderCourses 订单中所有的课程集合 筛选 该范围内的课程
                availableCourse = orderCourses.stream()
                        .filter(orderCourseDTO -> scopeIds.contains(orderCourseDTO.getCateId()))
                        .collect(Collectors.toList());
            }
            if (CollUtils.isEmpty(availableCourse)){
                continue;//说明当前优惠券限定了范围，但在订单中的课程中没有找到可用课程，说明该券不可用，进行下一个券的处理
            }

            //3.计算该优惠券可用课程的总金额
            int totalAmount = availableCourse.stream().mapToInt(OrderCourseDTO::getPrice).sum();

            //4.判断该优惠券是否可用 如果可用 添加到map中
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (discount.canUse(totalAmount,coupon)){
                map.put(coupon,availableCourse);
            }
        }
       return map;
    }
}
